package com.geek.enhance;

import com.geek.ChainInvocationContext;
import com.geek.ReflectiveInvocationContext;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.interceptor.InvocationContext;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class Enhance {

    public static <T> T enhance(T source, Class<? super T> type, Object... interceptors) {
        //走jdk动态代理
        if (type.isInterface()) {
            ClassLoader classLoader = type.getClassLoader();
            JDKHandlerAdapter jdkHandlerAdapter = new JDKHandlerAdapter(source, interceptors);
            return (T) Proxy.newProxyInstance(classLoader, new Class[]{type}, jdkHandlerAdapter);
        }
        //走cglib动态代理
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(type);
        CGLIBMethodInterceptorAdapter cglibMethodInterceptorAdapter = new CGLIBMethodInterceptorAdapter(source, interceptors);
        enhancer.setCallback(cglibMethodInterceptorAdapter);
        return (T) enhancer.create();
    }

    static class JDKHandlerAdapter implements InvocationHandler{

        private final Object source;

        private final Object[] interceptors;

        public JDKHandlerAdapter(Object source, Object... interceptors) {
            this.source = source;
            this.interceptors = interceptors;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            InvocationContext delegateContext = new ReflectiveInvocationContext(source, method, args);
            ChainInvocationContext context = new ChainInvocationContext(delegateContext, interceptors);
            return context.proceed();
        }

    }


    static class CGLIBMethodInterceptorAdapter implements MethodInterceptor {

        private final Object target;

        private final Object[] interceptors;

        public CGLIBMethodInterceptorAdapter(Object target, Object[] interceptors) {
            this.target = target;
            this.interceptors = interceptors;
        }

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            InvocationContext delegateContext = new ReflectiveInvocationContext(obj, method, args);
            ChainInvocationContext context = new ChainInvocationContext(delegateContext, interceptors);
            return context.proceed();
        }
    }


}
